package com.zzh.partnersys.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Pair;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.zzh.partnersys.chat.session.SessionFactory;
import com.zzh.partnersys.common.cache.RedisCacheUtil;
import com.zzh.partnersys.common.exception.BusinessException;
import com.zzh.partnersys.common.exception.ResultCodeEnum;
import com.zzh.partnersys.common.util.TokenUtils;
import com.zzh.partnersys.entity.UserDO;
import com.zzh.partnersys.entity.dto.UserTDO;
import com.zzh.partnersys.entity.vo.UserAvaGenVO;
import com.zzh.partnersys.mapper.UserMapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zzh.partnersys.utils.*;
import com.zzh.partnersys.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.geo.Point;
import org.springframework.data.redis.core.GeoOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.DigestUtils;

import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static com.zzh.partnersys.common.exception.ResultCodeEnum.*;
import static com.zzh.partnersys.constant.MD5PasswordFlagPrefix.USER_PASSWORD_PREFIX;
import static com.zzh.partnersys.constant.UserConstant.*;


/**
 * <p>
 * 用户�?服务实现�?
 * </p>
 *
 * @author zzh
 * @since 2024-07-13
 */
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, UserDO> implements UserService {

    @Resource
    private UserMapper userMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Resource
    private com.zzh.partnersys.es.service.ElasticsearchSyncService elasticsearchSyncService;

//    @Autowired
//    private UserRepository userRepository;

    /**
     * 用户注册(邮箱形式) =>成功返回 新的账户 id
     * @param  email 邮箱
     * @param  password 密码
     * @param  code 验证码
     * @return 账户
     */
    @Override
    public String userRegisterEmail(String email , String password , String code){
        //1.判断code是否正确
        String codeRedis = stringRedisTemplate.opsForValue().get(USER_LOCK_CODE_EMAIL_KEY + email);
        if (StrUtil.isBlank(codeRedis)|| !code.equals(codeRedis)){
            throw new BusinessException("验证码不正确",40000);
        }

        // 2. 密码加密
        //使用Spring的加密方法，采用MD5加密方式
        String encryptPassword = DigestUtils.md5DigestAsHex((USER_PASSWORD_PREFIX + password).getBytes(StandardCharsets.UTF_8));

        //避免多次插入数据
        RLock lock = redissonClient.getLock(USER_LOCK_REGISTER_EMAIL_KEY + email);
        try {
            //1.保证只能一个线程拿到锁
            if(lock.tryLock(0,30000L,TimeUnit.MILLISECONDS)){
                //检查我们的email是否已经注册过了
                //select id from userDO where email='';
                UserDO userDOEmail = baseMapper.selectOne(new LambdaQueryWrapper<UserDO>().eq(UserDO::getEmail, email).select(UserDO::getId));
                if (ObjUtil.isNotNull(userDOEmail)){//存在user
                    throw new BusinessException(ResultCodeEnum.EXIST_USER);
                }

                // 3. 向用户数据库插入数据
                UserDO userDO = new UserDO();

                //生成12位
                String planetCode = PlanetCodeUtils.getPlanetCodeByUUID();
                userDO.setPlanetCode(planetCode);

                //生成11位账户
                String account = UserAccountUtils.getUserAccountByTimeStamp();
                userDO.setUserAccount(account);

                userDO.setEmail(email);
                userDO.setUserPassword(encryptPassword);
                boolean saveResult = this.save(userDO);//插入数据
                if (!saveResult) {
                    throw new BusinessException(ResultCodeEnum.SYSTEM_ERROR);
                }
                // ES 同步用户数据
                try {
                    elasticsearchSyncService.syncUserToEs(userDO);
                } catch (Exception e) {
                    log.error("同步用户到 ES 失败，userId: {}", userDO.getId(), e);
                }
                return account;
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }finally {
            //2.保证自己释放自己的锁
            if (lock.isHeldByCurrentThread()){
                lock.unlock();
            }
        }
        return null;
    }

    /**
     * 用户注册(手机号形式 =>成功返回 新的账户
     *
     * @param phone  手机�?
     * @param userPassword 用户密码
     * @param code         验证�?
     * @return
     */
    @Override
    public String userRegisterPhone(String phone, String userPassword, String code){
        //1.判断code是否正确
        String codeRedis = stringRedisTemplate.opsForValue().get(USER_LOCK_CODE_PHONE_KEY + phone);
        if (codeRedis == null || !code.equals(codeRedis)){
            throw new BusinessException("验证码不正确",40000);
        }

        // 2. 密码加密
        //使用Spring的加密方法，采用MD5加密方式
        String encryptPassword = DigestUtils.md5DigestAsHex((USER_PASSWORD_PREFIX + userPassword).getBytes(StandardCharsets.UTF_8));

        //避免多次插入数据
        RLock lock = redissonClient.getLock(USER_LOCK_REGISTER_PHONE_KEY + phone);
        try {
            //1.保证只能一个线程拿到锁
            if(lock.tryLock(0,30000L,TimeUnit.MILLISECONDS)){
                //检查我们的email是否已经注册过了
                //select id from userDO where email='';
                UserDO userDOEmail = baseMapper.selectOne(new QueryWrapper<UserDO>().eq("phone", phone).select("id"));
                if (userDOEmail != null){//存在user =》email要做为索�?
                    throw new BusinessException(ResultCodeEnum.EXIST_USER);
                }

                // 3. 向用户数据库插入数据
                UserDO userDO = new UserDO();

                //生成12位编�?
                String planetCode = PlanetCodeUtils.getPlanetCodeByUUID();
                userDO.setPlanetCode(planetCode);

                //todo 生成11位账�?>怎么解决更好，这里只能解�?个人同一时间的不同账户返回，超过8个可能就会出现一定的错误
                String account = UserAccountUtils.getUserAccountByTimeStamp();
                userDO.setUserAccount(account);

                userDO.setPhone(phone);
                userDO.setUserPassword(encryptPassword);
                boolean saveResult = this.save(userDO);//插入数据
                if (!saveResult) {
                    throw new BusinessException(ResultCodeEnum.SYSTEM_ERROR);
                }
                // ES 同步用户数据
                try {
                    elasticsearchSyncService.syncUserToEs(userDO);
                } catch (Exception e) {
                    log.error("同步用户到 ES 失败，userId: {}", userDO.getId(), e);
                }
                return account;
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }finally {
            //2.保证自己释放自己的锁
            if (lock.isHeldByCurrentThread()){
                lock.unlock();
            }
        }

        return null;
    }


    /**
     * 邮箱登录
     * @param email 邮箱
     * @param code 验证码
     * @param request 请求对象
     * @return 用户信息
     */
    @Override
    public UserDO userLoginEmail(String email, String code, HttpServletRequest request) {
        //2.判断验证码是否正确
        String codeRedis = stringRedisTemplate.opsForValue().get(USER_LOCK_CODE_EMAIL_KEY + email);
        if (!code.equals(codeRedis)){
            throw new BusinessException("验证码不正确",40000);
        }
        //1.判断是否注册了！
        UserDO userDO = userMapper.selectOne(new LambdaQueryWrapper<UserDO>().eq(UserDO::getEmail, email));
        if (userDO == null){
            throw new BusinessException("邮箱未注册",40000);
        }
        // 3. 用户脱敏
        UserDO safetyUserDO = getSafeUser(userDO);
        // 4. 不再设置 Session，改为返回用户信息，由 Controller 生成 Token
        if (safetyUserDO != null){
            //开启异步线程执行
            try {
                UserService proxy = (UserServiceImpl) AopContext.currentProxy();
                proxy.geoLocation(safetyUserDO.getId(),request);//这样才是异步线程
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        return safetyUserDO;
    }

    /**
     * 手机号登录
     * @param phone
     * @param code
     * @param request
     * @return
     */
    @Override
    public UserDO userLoginPhone(String phone, String code, HttpServletRequest request) {
        //2.判断验证码是否正�?—�?减少数据库开�?
        String codeRedis = stringRedisTemplate.opsForValue().get(USER_LOCK_CODE_PHONE_KEY + phone);
        if (!code.equals(codeRedis)){
            throw new BusinessException("验证码不正确",40000);
        }
        //1.判断是否注册了！
        UserDO userDO = userMapper.selectOne(new QueryWrapper<UserDO>().eq("phone", phone));
        if (userDO == null){
            throw new BusinessException("手机号未注册",40000);
        }
        // 3. 用户脱敏
        UserDO safetyUserDO = getSafeUser(userDO);
        // 4. 记录用户登录�?
        request.getSession().setAttribute(USER_LOGIN_STATE, safetyUserDO);
        if (safetyUserDO != null){
            //开启异步线程执�?
            try {
                UserService proxy = (UserServiceImpl) AopContext.currentProxy();
                proxy.geoLocation(safetyUserDO.getId(),request);//这样才是异步线程
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        return safetyUserDO;
    }

    /**
     * 更新用户标签 + 性别 ，同时修改状态变为不是刚注册
     *
     * @param tagNameList
     * @param gender
     * @param request
     */
    @Override
    public void updateUserTagsAndGender(List<String> tagNameList,Integer gender, HttpServletRequest request) {
        UserDO currentUserDO = getCurrentUser(request);
        Gson gson = new Gson();
        String json = gson.toJson(tagNameList);
        System.out.println("tagNameList:"+json);
        currentUserDO.setTags(json);
        currentUserDO.setUserStatus(0);//设置为正�?从刚注册->正常)
        Integer isUpdate = 0;
        if (gender == null){
            //表示默认为空了，性别默认就是�?前端传过来的性别就是�?
            isUpdate = userMapper.updateTagsAndStatusById(currentUserDO.getId(),json);
        }else {
            isUpdate = userMapper.updateTagsAndStatusAndGenderById(currentUserDO.getId(),json);
        }
        if (isUpdate == 0){
            throw new BusinessException(ResultCodeEnum.SYSTEM_ERROR);//异常
        }
        // ES 同步用户数据
        try {
            // 重新查询最新数据
            UserDO updatedUser = this.getById(currentUserDO.getId());
            if (updatedUser != null) {
                elasticsearchSyncService.syncUserToEs(updatedUser);
            }
        } catch (Exception e) {
            log.error("同步用户到 ES 失败，userId: {}", currentUserDO.getId(), e);
        }
        // 更新成功后，删除缓存，让下次查询时重新从数据库加载并缓存
        String cacheKey = USER_CACHE_INFO_KEY + currentUserDO.getId();
        RedisCacheUtil.delete(redisTemplate, cacheKey);
        log.debug("用户标签和性别已更新，删除缓存，userId: {}", currentUserDO.getId());
    }


    /**
     * 用户登录
     *
     * @param userAccount  用户账户
     * @param userPassword 用户密码
     * @param request      请求
     * @return 脱敏后的用户信息
     * @deprecated 过期
     */
    @Override
    public UserDO userLoginAccount(String userAccount, String userPassword , HttpServletRequest request) {
        // 1. 校验
        // 1.1 校验账户
        if (userAccount.length() != 11){
            throw new BusinessException(ResultCodeEnum.PARAMS_ERROR);
        }
        //1.2校验密码
        if (userPassword.length() < 8){
            throw new BusinessException("密码不正确",40000);
        }

        //1.3校验验证码（前端校验�?
//        String codeRedis = stringRedisTemplate.opsForValue().get(USER_LOCK_CODE_EMAIL_KEY + userAccount);
//        if (!code.equals(codeRedis)){
//            throw new BusinessException("验证码不正确",40000);
//        }

        // 2. 校验密码是否输入正确，和数据库中的密文密码去对比
        final String SALT = "mark";
        String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes(StandardCharsets.UTF_8));
        //查询用户是否存在且密码与之匹�?
        LambdaQueryWrapper<UserDO> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserDO::getUserAccount, userAccount)
                .eq(UserDO::getUserPassword, encryptPassword);
        //这里需要添加逻辑删除 以防查询到已删除�?逻辑删除使用方法：①配置yml ②在实体类字段添加@TableLogic
        UserDO userDO = baseMapper.selectOne(queryWrapper);

        if (userDO == null){
            throw new BusinessException("用户名或密码错误",40000);
        }

        // 3. 用户脱敏
        UserDO safetyUserDO = getSafeUser(userDO);

        // 4. 记录用户登录�?
        request.getSession().setAttribute(USER_LOGIN_STATE, safetyUserDO);
        if (safetyUserDO != null){
            //开启异步线程执�?
            try {
                UserService proxy = (UserServiceImpl) AopContext.currentProxy();
                proxy.geoLocation(safetyUserDO.getId(),request);//这样才是异步线程
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
//        System.out.println("证明异步线程执行成功�?);
        return safetyUserDO;
    }


    /**
     * 用户注册
     *
     * @param userAccount   用户账户
     * @param userPassword  用户密码
     * @param checkPassword 校验密码
     * @return 新用户id
     */
    @Override
    public long userRegister(String userAccount, String userPassword, String checkPassword) {
        //1.校验
        // 1.1 校验是否为空 引入apache common utils ：Apache Commons Lang 使用其中的方法：isAnyBlank 可判断多个字符串是否为空
        if(StringUtils.isAllBlank(userAccount,userPassword,checkPassword)){
            throw new BusinessException(ResultCodeEnum.FORM_DATA_ERROR);
        }

        //2.账户不少�?�?
        if (userAccount.length() < 4) {
            throw new BusinessException(ResultCodeEnum.FORM_DATA_ERROR);
        }


        //3.密码不少�?�?
        if (userPassword.length() < 8 || checkPassword.length() < 8) {
            throw new BusinessException(ResultCodeEnum.FORM_DATA_ERROR);
        }

        // 1.5 校验账号不包含特殊字�?
        /*  pP和pS匹配特殊符号
            \s+是空格一个或者多�?不管在那个位置都能匹�?
            \pP 其中的小�?p �?property 的意思，表示 Unicode 属性，用于 Unicode 正表达式的前缀�?
            大写 P 表示 Unicode 字符集七个字符属性之一：标点字符�?
            大写 P 表示 符号（比如数学符号、货币符号等�?
        */
        String validPattern = "[\\u00A0\\s\"`~!@#$%^&*()+=|{}':;',\\[\\].<>/?~！@#�?…�?*（）—�?|{}【】‘；：”�?。，、？]";
        Matcher matcher = Pattern.compile(validPattern).matcher(userAccount);
        if (matcher.find()){//满足匹配规则(也就是存在特殊字�?
            throw new BusinessException(ResultCodeEnum.FORM_DATA_ERROR);
        }

        // 1.6 校验密码和校验密码相�?
        if (!userPassword.equals(checkPassword)) {
            throw new BusinessException(ResultCodeEnum.FORM_DATA_ERROR);
        }

        // 1.4 校验账号不能重复 放在最�?当其他校验通过�? 再去数据库查询账号是否存�?防止性能浪费
        LambdaQueryWrapper<UserDO> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserDO::getUserAccount, userAccount);
        long count = baseMapper.selectCount(queryWrapper);
        if (count > 0) {
            throw new BusinessException(ResultCodeEnum.EXIST_USER);
        }

        // 2. 密码加密
        //使用Spring的加密方法，采用MD5加密方式
        String encryptPassword = DigestUtils.md5DigestAsHex((USER_PASSWORD_PREFIX + userPassword).getBytes(StandardCharsets.UTF_8));

        // 3. 向用户数据库插入数据
        UserDO userDO = new UserDO();
        userDO.setUserAccount(userAccount);
        userDO.setUserPassword(encryptPassword);
        boolean saveResult = this.save(userDO);
        if (!saveResult) {
            throw new BusinessException(ResultCodeEnum.SYSTEM_ERROR);
        }
        // ES 同步用户数据
        try {
            elasticsearchSyncService.syncUserToEs(userDO);
        } catch (Exception e) {
            log.error("同步用户到 ES 失败，userId: {}", userDO.getId(), e);
        }
        return userDO.getId();
    }


    /**
     * @param username 用户�?
     * @param request 请求对象
     * @return 用户列表(脱敏后的User)
     * @description 根据用户名查询用�?
     */
    @Override
    public List<UserDO> searchUsers(String username, HttpServletRequest request) {
        if (!isAdmin(request)){
            return new ArrayList<>();
        }
        LambdaQueryWrapper<UserDO> queryWrapper = new LambdaQueryWrapper<>();
        if (StringUtils.isNotBlank(username)) {
            queryWrapper.like(UserDO::getUsername, username);
        }
        return this.list(queryWrapper).stream().map(this::getSafeUser).collect(Collectors.toList());
    }

    /**
     * 鉴权函数：判断是否为管理�?仅管理员可查询、删�?
     *
     * @param request 请求
     * @return 是否为管理员
     */
    @Override
    public boolean isAdmin(HttpServletRequest request){
        //鉴权：仅管理员可查询
        try {
            UserDO userDO = getCurrentUser(request);
            return userDO != null && userDO.getUserRole() == ADMIN_ROLE;
        } catch (Exception e) {
            return false;
        }
    }


    /**
     * 用户注销退�?
     * @param request
     * @return
     */
    @Override
    public boolean userLogout(HttpServletRequest request) {
        UserDO currentUserDO = getCurrentUser(request);
        //移除Session中的登录�?
        request.getSession().removeAttribute(USER_LOGIN_STATE);
        //解除绑定
        SessionFactory.getSession().unbind(currentUserDO.getId());
        return true;
    }


    /**
     * 根据标签列表,查询出相关的所有用�?SQL方式)
     * @param tagNameList 标签名的列表
     * @return
     */
    public List<UserDO> searchUsersByTagsBySQL(List<String> tagNameList) {
        //1.先判断是否为空防止传过来的tag是空�?
        if (CollectionUtils.isEmpty(tagNameList)){
            //自定义异常抛出！
            throw new BusinessException(ResultCodeEnum.FORM_DATA_ERROR);
        }
        //long startTime = System.currentTimeMillis();//为了计算两者的时间策略

        //2.拼接查询语句sql=> 用户表中的tags字段 like '' and like ''
        QueryWrapper<UserDO> userQueryWrapper = new QueryWrapper<>();
        for (String tagName : tagNameList) {
            userQueryWrapper = userQueryWrapper.like("tags", tagName);
        }

        //3.查询出标签对应的用户
        IPage iPage = new Page(1,10);
        IPage iPage1 = userMapper.selectPage(new Page(1,10), userQueryWrapper);
        List<UserDO> userDOList = iPage1.getRecords();
        userDOList = userDOList.stream().map(this::getSafeUser).collect(Collectors.toList());

        return userDOList;
    }

    /**
     * 根据标签列表,查询出相关的所有用�?内存方式 + 用户查询其实也是用了sql)
     * @param tagNameList 标签名的列表
     * @return
     */
    public List<UserDO> searchUsersByTagsByCache(List<String> tagNameList) {
        //1.先判断是否为空防止传过来的tag是空�?
        if (CollectionUtils.isEmpty(tagNameList)){
            //自定义异常抛出！
            throw new BusinessException(ResultCodeEnum.FORM_DATA_ERROR);
        }
        long startTime = System.currentTimeMillis();//为了计算两者的时间策略

        //2.查询出所有的用户
        List<UserDO> userDOList = userMapper.selectList(null);

        Gson gson = new Gson();
        //3.在内存中进行过滤不符合标签列表的(and形式�? stream流过滤每一个是否符合的标签的条�?
        userDOList = userDOList.stream().filter((user) -> {
            String tages = user.getTags();
            if (StringUtils.isBlank(tages)) {
                return false;
            }
            //获取到用户tags字段的json字符�?转成 =�?Set对象
            Set<String> tageNameSet = gson.fromJson(tages, new TypeToken<Set<String>>() {}.getType());

            //判空,java8的特�?如果tageNameSet是空了，就返回new HashSet<>()！（if判断空也可以�?
            tageNameSet = Optional.ofNullable(tageNameSet).orElse(new HashSet<>());

            //检查是否符合标签列�?
            for (String tagName : tagNameList) {
                if (!tageNameSet.contains(tagName)) {
                    return false;//and  必须都得包含
                }
            }
            return true;
        }).map(this::getSafeUser).collect(Collectors.toList());

        log.info("memory query time = " + (System.currentTimeMillis() - startTime));
        return userDOList;

    }

    /**
     * 用户脱敏
     *
     * @param originUserDO 完成用户信息
     * @return 脱敏后的用户信息
     */
    @Override
    public UserDO getSafeUser(UserDO originUserDO){
        if (originUserDO == null){
            return null;
        }
        UserDO userDO = new UserDO();
        userDO.setId(originUserDO.getId());
        userDO.setUserAccount(originUserDO.getUserAccount());
        userDO.setPhone(originUserDO.getPhone());
        userDO.setGender(originUserDO.getGender());
        userDO.setUsername(originUserDO.getUsername());
        userDO.setUserStatus(originUserDO.getUserStatus());
        userDO.setUserRole(originUserDO.getUserRole());
        userDO.setAvatarUrl(originUserDO.getAvatarUrl());
        userDO.setCreateTime(originUserDO.getCreateTime());
        userDO.setPlanetCode(originUserDO.getPlanetCode());
        userDO.setEmail(originUserDO.getEmail());
        userDO.setPersonalProfile(originUserDO.getPersonalProfile());
        userDO.setUpdateTime(originUserDO.getUpdateTime());
        userDO.setTags(originUserDO.getTags());
        userDO.setSkillCert(originUserDO.getSkillCert());
        userDO.setAvailTime(originUserDO.getAvailTime());
        userDO.setCollabPrefer(originUserDO.getCollabPrefer());
        userDO.setCollabCredit(originUserDO.getCollabCredit());
        userDO.setUserType(originUserDO.getUserType());
        userDO.setMemberLevel(originUserDO.getMemberLevel());
        return userDO;
    }


    /**
     * 更改用户信息
     * @param userTDO
     * @param currentUserDO
     * @return
     */
    @Override
    public boolean updateUser(UserTDO userTDO, UserDO currentUserDO, HttpServletRequest request) {
        //判断修改信息是否存在
        String key = userTDO.getEditKey();
        String name = userTDO.getEditName();
        String value = userTDO.getEditValue();
        String editValueNew = userTDO.getEditValueNew();
        if (StringUtils.isAllBlank(key,name,value)){
            throw new BusinessException(ResultCodeEnum.PARAMS_ERROR);
        }
        //修改 可修改有 phone , avatarUrl,email,gender,username, personalProfile�?
        if (key.equals("phone")){
            currentUserDO.setPhone(value);
        }
        if (key.equals("userPassword")){
            //加密密码
            //使用Spring的加密方法，采用MD5加密方式
            String encryptPassword = DigestUtils.md5DigestAsHex((USER_PASSWORD_PREFIX + value).getBytes(StandardCharsets.UTF_8));
            if (!currentUserDO.getUserPassword().equals(encryptPassword)) {
                throw new BusinessException("原密码不正确",40001);
            }
            if (StringUtils.isBlank(editValueNew)){
                throw new BusinessException(ResultCodeEnum.PARAMS_ERROR);
            }
            editValueNew = DigestUtils.md5DigestAsHex((USER_PASSWORD_PREFIX + editValueNew).getBytes(StandardCharsets.UTF_8));
            userTDO.setEditValueNew(editValueNew);
        }
        if (key.equals("email")){
            currentUserDO.setEmail(value);
        }
        if (key.equals("username")){
            currentUserDO.setUsername(value);
        }
        if (key.equals("gender")){
            if (value.equals("男")){
                userTDO.setGenderInt(0);
                currentUserDO.setGender(0);
            }else {
                userTDO.setGenderInt(1);
                currentUserDO.setGender(1);
            }
        }
        if (key.equals("personalProfile")){
            currentUserDO.setPersonalProfile(value);
        }
        // 新增字段的更新逻辑
        if (key.equals("skillCert")){
            currentUserDO.setSkillCert(value);
        }
        if (key.equals("availTime")){
            currentUserDO.setAvailTime(value);
        }
        if (key.equals("collabPrefer")){
            currentUserDO.setCollabPrefer(value);
        }
        if (key.equals("collabCredit")){
            // 协作信用分只能由系统更新，这里可以添加权限控�?
            if (currentUserDO.getUserRole() != 1) { // 只有管理员可以更�?
                throw new BusinessException(NO_AUTH);
            }
            try {
                Double credit = Double.parseDouble(value);
                // 信用分范围验�?
                if (credit < 0 || credit > 10) {
                    throw new BusinessException(CREDIT_SCORE_ERROR);
                }
                currentUserDO.setCollabCredit(credit);
            } catch (NumberFormatException e) {
                throw new BusinessException(CREDIT_SCORE_FORMAT_ERROR);
            }
        }
        if (key.equals("userType")){
            try {
                Integer type = Integer.parseInt(value);
                // 用户类型范围验证
                if (type < 0 || type > 2) {
                    throw new BusinessException(USER_TYPE_INVALID);
                }
                currentUserDO.setUserType(type);
            } catch (NumberFormatException e) {
                throw new BusinessException(USER_TYPE_FORMAT_ERROR);
            }
        }
        if (key.equals("memberLevel")){
            try {
                Integer level = Integer.parseInt(value);
                // 会员等级范围验证
                if (level < 0 || level > 2) {
                    throw new BusinessException(MEMBER_LEVEL_INVALID);
                }
                currentUserDO.setMemberLevel(level);
            } catch (NumberFormatException e) {
                throw new BusinessException(MEMBER_LEVEL_FORMAT_ERROR);
            }
        }
        int i = userMapper.updateUserInfoById(userTDO);//动态修改sql
        if (i > 0){
            // 更新成功后，删除缓存，让下次查询时重新从数据库加载并缓存
            String cacheKey = USER_CACHE_INFO_KEY + currentUserDO.getId();
            RedisCacheUtil.set(redisTemplate, cacheKey,currentUserDO,30*60L);
            log.debug("用户信息已更新，删除缓存，userId: {}", currentUserDO.getId());
            
            // ES 同步用户数据
            try {
                // 重新查询最新数据
                UserDO updatedUser = this.getById(currentUserDO.getId());
                if (updatedUser != null) {
                    elasticsearchSyncService.syncUserToEs(updatedUser);
                }
            } catch (Exception e) {
                log.error("同步用户到 ES 失败，userId: {}", currentUserDO.getId(), e);
            }
            
            // 使用 Token 后，不再需要更新 Session，用户信息从数据库实时查询
            if (key.equals("username")){
                //更新用户信息=》es
//                UserES userES = userRepository.findById(uid);
//                userES.setUsername(value);
//                userES.setValue(value + " " + userES.getUserAccount());
//                userRepository.save(userES);
            }
            return true;
        }
        return false;
    }


    /**
     * 获取当前用户
     * @param request 请求
     * @return 当前用户
     */
    @Override
    public UserDO getCurrentUser(HttpServletRequest request) {
        if (request == null){
            throw new BusinessException(ResultCodeEnum.PARAMS_ERROR);
        }
        // 从 Token 中获取用户ID
        Long userId = TokenUtils.getCurrentUserIdOrThrow(request);
        // 从数据库查询用户信息 (todo 做一层缓存)
        UserDO userDO = this.getById(userId);
        if (userDO == null){
            throw new BusinessException(NO_AUTH);
        }
        // 返回脱敏后的用户信息
        return getSafeUser(userDO);
    }


    /**
     * 获取当前用户
     * @param userId 用户ID
     * @return 当前用户
     */
    @Override
    public UserDO getCurrentUserById(Long userId) {
        if (userId == null){
            throw new BusinessException(ResultCodeEnum.PARAMS_ERROR);
        }

        // 构建缓存key
        String cacheKey = USER_CACHE_INFO_KEY + userId;

        // 先从缓存中获取
        UserDO userDO = RedisCacheUtil.get((RedisTemplate<String, UserDO>) redisTemplate, cacheKey);
        if (userDO != null) {
            log.debug("从缓存中获取用户信息，userId: {}", userId);
            // 返回脱敏后的用户信息
            return getSafeUser(userDO);
        }

        // 缓存中没有，从数据库查询
        log.debug("缓存中不存在，从数据库查询用户信息，userId: {}", userId);
        userDO = this.getById(userId);
        if (userDO == null){
            throw new BusinessException(NO_AUTH);
        }

        // 将用户信息存入缓存，设置过期时间为 30 分钟
        RedisTemplate<String, UserDO> userRedisTemplate = (RedisTemplate<String, UserDO>) redisTemplate;
        RedisCacheUtil.set(userRedisTemplate, cacheKey, userDO, 30 * 60L);
        log.debug("用户信息已存入缓存，userId: {}", userId);

        // 返回脱敏后的用户信息
        return getSafeUser(userDO);
    }


    /**
     * 返回推荐用户列表信息(分页)
     * @param limitLong 起始索引
     * @param pageSize 每页记录数
     * @param request 请求
     * @return 推荐用户列表
     */
    @Override
    public List<UserDO> getRecommendUserList(long limitLong, long pageSize, HttpServletRequest request) {
        //获取当前用户
        UserDO currentUserDO = getCurrentUser(request);
        //根据当前用户id来缓�?获取推荐用户列表  (如果redis中有数据)
        String key = USER_CACHE_RECOMMEND_KEY + currentUserDO.getId();
        ValueOperations<String,Object> valueOperations = redisTemplate.opsForValue();
        List<UserDO> userDOList = (List<UserDO>)valueOperations.get(key);
        if (userDOList != null){
            return userDOList;
        }
        //redis中没数据
        if (limitLong <= 0){
            limitLong = 0;
        }else {
            limitLong--;
        }
        //调用自定义联表查询方法(优化sql查询)
        userDOList = userMapper.customPaginationQuery(limitLong, pageSize);
        if (userDOList == null) {
            throw new BusinessException(ResultCodeEnum.FAIL);
        }
        //返回且缓存在redis�?
        List<UserDO> safetyUserListDO = userDOList.stream().map(this::getSafeUser).collect(Collectors.toList());
        valueOperations.set(key, safetyUserListDO,30, TimeUnit.MINUTES);//设置过期时间！！
        return safetyUserListDO;
    }

    /**
     * 匹配用户(编辑距离算法实现)
     * @param num 匹配数
     * @param currentUserDO 当前用户
     * @return 匹配用户列表
     */
    @Override
    public List<UserDO> matchUserListDistance(long num, UserDO currentUserDO) {
        List<UserDO> userDOList = matchUserListDistanceQueue(num, currentUserDO);
        return userDOList;
    }



    //List存储，比较完编辑距离后，一个个插入，最后进行统一排序
    public List<UserDO> matchUserListDistanceList(long num, UserDO currentUserDO) {
        //1.list获取所有用�?
        QueryWrapper<UserDO> userQueryWrapper = new QueryWrapper<>();
        userQueryWrapper.isNotNull("tags");
        userQueryWrapper.select("id","tags");
        List<UserDO> userDOList = userMapper.selectList(userQueryWrapper);
        String tags = currentUserDO.getTags();
        long currentUserId = currentUserDO.getId();
        //2.tags json转集�?
        Gson gson = new Gson();
        List<String> tagsList1 = gson.fromJson(tags, new TypeToken<List<String>>() {
        }.getType());
        //3.创建List 一个键值对 distance，UserDO
        List<Pair<Integer, UserDO>> distanceUserList = new ArrayList<>();
        for (UserDO userDO : userDOList) {
            //4.剔除自己
            if (userDO.getId() == currentUserId){
                continue;
            }
            String userTags = userDO.getTags();
            if (userTags == null){
                continue;
            }
            List<String> tagsList2 = gson.fromJson(userTags, new TypeToken<List<String>>() {}.getType());
            //5.调用编辑距离算法,计算分数
            int score = AlgorithmUtils.minDistanceTagsList(tagsList1, tagsList2);
            distanceUserList.add(new Pair<>(score, userDO));
        }
        // 6.使用自定义比较器 根据分数排序，并且取出前几个  截取�?Top num用户信息
        List<Pair<Integer, UserDO>> limitNumDistanceUserMatch = distanceUserList.stream().sorted(new Comparator<Pair<Integer, UserDO>>() {
            @Override
            public int compare(Pair<Integer, UserDO> o1, Pair<Integer, UserDO> o2) {
                return o1.getKey() - o2.getKey();
            }
        }).limit(num).collect(Collectors.toList());


        //6.截取�?Top num用户信息
        List<Long> orderIdList = limitNumDistanceUserMatch.stream().map(pair -> pair.getValue().getId()).limit(num).collect(Collectors.toList());
        List<UserDO> userDOListTopNum = userMapper.selectByIds(orderIdList);
        Map<Long, UserDO> unOrderUserList = userDOListTopNum.stream().collect(Collectors.toMap(UserDO::getId, user -> user));

        userDOListTopNum = new ArrayList<>();
        for (Long idKey : orderIdList) {
            userDOListTopNum.add(unOrderUserList.get(idKey));
        }

        //7.脱敏
        userDOListTopNum = userDOListTopNum.stream().map(this::getSafeUser).collect(Collectors.toList());
        return userDOListTopNum;
    }

    //优先队列，编辑距离算法计算完毕后，插入时进行判断限制固定长度�?
    public List<UserDO> matchUserListDistanceQueue(long num, UserDO currentUserDO) {
        //1.list获取所有用�?
        QueryWrapper<UserDO> userQueryWrapper = new QueryWrapper<>();
        userQueryWrapper.isNotNull("tags");
        userQueryWrapper.select("id","tags");
        List<UserDO> userDOList = userMapper.selectList(userQueryWrapper);
        String tags = currentUserDO.getTags();
        long currentUserId = currentUserDO.getId();
        //2.tags json转集�?
        Gson gson = new Gson();
        List<String> tagsList1 = gson.fromJson(tags, new TypeToken<List<String>>() {
        }.getType());


        //就表示刚登录的用户一般没有tag标签�?随机) =>新增一个页面可以去判断新用户，显示标签选择
        if (tagsList1 == null){
            IPage<UserDO> page = new Page(10,20);
            IPage<UserDO> userIPage = userMapper.selectPage(page, new QueryWrapper<UserDO>().notIn("id", currentUserId));
            List<UserDO> records = userIPage.getRecords();
            //脱敏
            records = records.stream().map(this::getSafeUser).collect(Collectors.toList());
            return records;
        }

        //3.创建Queue 一个键值对 distance，UserDO
        PriorityQueue<Pair<Integer, UserDO>> priorityQueue = new PriorityQueue<>((a, b) -> a.getKey() - b.getKey());
        for (UserDO userDO : userDOList) {
            //4.剔除自己
            if (userDO.getId() == currentUserId){
                continue;
            }
            String userTags = userDO.getTags();
            if (userTags == null){
                continue;
            }
            List<String> tagsList2 = gson.fromJson(userTags, new TypeToken<List<String>>() {}.getType());
            //5.调用编辑距离算法,计算分数
            int score = AlgorithmUtils.minDistanceTagsList(tagsList1, tagsList2);
            //限制大小插入
            if (priorityQueue.size() < num){
                priorityQueue.offer(new Pair<>(score, userDO));
            }else {
                if (priorityQueue.peek().getKey() > score) {
                    priorityQueue.poll();
                    priorityQueue.offer(new Pair<>(score, userDO));
                }
            }
        }
        //直接返回队列中的User
        //6.截取�?Top num用户信息
        List<Long> orderIdList = priorityQueue.stream().map(pair -> pair.getValue().getId()).collect(Collectors.toList());
        if (CollUtil.isEmpty(orderIdList)){
            return new ArrayList<>();
        }
        List<UserDO> userDOListTopNum = userMapper.selectByIds(orderIdList);
        //分组id->user => 排序
        Map<Long, UserDO> unOrderUserList = userDOListTopNum.stream().collect(Collectors.toMap(UserDO::getId, user -> user));
        userDOListTopNum = new ArrayList<>();
        for (Long idKey : orderIdList) {
            userDOListTopNum.add(unOrderUserList.get(idKey));
        }
        //7.脱敏
        userDOListTopNum = userDOListTopNum.stream().map(this::getSafeUser).collect(Collectors.toList());
        return userDOListTopNum;
    }


    /**
     * 异步修改用户的头像和缓存
      * @param url
     * @param request
     */
    @Override
    public void updateUserAvatarUrl(String url, HttpServletRequest request) {
        //redis修改
        UserDO currentUserDO = getCurrentUser(request);
        currentUserDO.setAvatarUrl(url);
        request.setAttribute(USER_LOGIN_STATE, currentUserDO);
        //数据库修�?
        int update = userMapper.updateAvaTarUrl(url, currentUserDO.getId());
    }

    /**
     * 根据用户id列表查询部分信息
     * @param idList
     * @return
     */
    @Override
    public List<UserAvaGenVO> selectAvatarUrlAndGender(List<Long> idList) {
        if (idList == null || idList.size() == 0){
            return null;
        }
        List<UserAvaGenVO> userAvaGenVOList = userMapper.selectAvaGenByIdList(idList);
        return userAvaGenVOList;
    }

    /**
     * 查询当前id的用户信�?
     * @param id
     * @return
     */
    @Override
    public UserDO selectUserById(Long id) {
        UserDO userDO = userMapper.selectById(id);
        if (userDO != null){
            userDO.setUserPassword(null);
            userDO.setCreateTime(LocalDateTime.now());//更新createTime(作为申请消息的时�?
        }
        return userDO;
    }


    /**
     * 根据账户查询用户列表
     * @param account
     * @return
     */
    @Override
    public List<UserDO> selectUserByAccount(String account) {
        long parseLong = Long.parseLong(account);
        List<UserDO> userDOList = userMapper.selectListByAccount(parseLong);
        return userDOList;
    }


    @Override
    public List<UserDO> selectUsersByIds(List<Long> idList) {
        List<UserDO> userDOList = userMapper.selectByIds(idList);
        userDOList.forEach(user -> getSafeUser(user));//过滤脱敏
        return userDOList;
    }

    @Autowired
    private GeoUtils geoUtils;

    // 地理位置信息 登录一次进行插入！地理位置
    @Async
    @Override
    public void geoLocation(long id, HttpServletRequest request) throws InterruptedException {
//        Thread.sleep(5000);
        // todo 后续注释打开
//        String currentIPGeoInfo = geoUtils.getCurrentIPGeoInfo(request.getRemoteAddr());
//        if (!currentIPGeoInfo.isEmpty()){
//            return;
//        }
//        String[] split = currentIPGeoInfo.split(",");
//        double lng = Double.parseDouble(split[0]);
//        double lat = Double.parseDouble(split[1]);
//        GeoOperations geoOperations = redisTemplate.opsForGeo();
//        geoOperations.remove(USER_CACHE_LOCATION_KEY,String.valueOf(id));
//        geoOperations.add(USER_CACHE_LOCATION_KEY, new Point(lng, lat), String.valueOf(id));
//        RGeo<String> findFriendGeo = redissonClient.getGeo(USER_CACHE_LOCATION_KEY);
//        findFriendGeo.add(new GeoEntry(lng, lat, String.valueOf(id)));
    }


}
